﻿using System;
using System.Threading;
using Microsoft.Ccr.Core;
using NUnit.Framework;
using XcoAppSpaces.Dialogs;

namespace test.Dialogs
{
    [TestFixture]
    public class Test_StatemachineDialog
    {
        [Test]
        public void Create_dialog()
        {
            new StatemachineDialog<SampleStates>(SampleStates.start);
        }

        [Test, ExpectedException(typeof(TypeInitializationException))]
        public void Fail_to_create_dialog_for_non_enum_state_type()
        {
            new StatemachineDialog<int>(0);
        }

        [Test]
        public void Check_current_state()
        {
            var d = new StatemachineDialog<SampleStates>(SampleStates.start);
            Assert.AreEqual(SampleStates.start, d.State);
        }

        [Test]
        public void Set_state()
        {
            var d = new StatemachineDialog<SampleStates>(SampleStates.start);
            d.State = SampleStates.next1;
            Assert.AreEqual(SampleStates.next1, d.State);
        }

        [Test]
        public void Add_transitions()
        {
            var are = new AutoResetEvent(false);

            var d = new StatemachineDialog<SampleStates>(SampleStates.start);
            d.Start();

            d.Transition<int>(SampleStates.start, i =>
                                                      {
                                                          d.State = SampleStates.next1;
                                                          are.Set();
                                                      });
            d.Transition<int>(SampleStates.next1, i =>
                                                      {
                                                          d.State = SampleStates.finish;
                                                          are.Set();
                                                      });

            d.Post(new DialogMessage(1, null));
            Assert.IsTrue(are.WaitOne(500));
            Assert.AreEqual(SampleStates.next1, d.State);

            d.Post(new DialogMessage(2, null));
            Assert.IsTrue(are.WaitOne(500));
            Assert.AreEqual(SampleStates.finish, d.State);
        }


        [Test]
        public void Define_transitions_with_fluent_interface()
        {
            var pex = new Port<Exception>();
            ICausality c = new Causality("test", pex);
            Arbiter.Activate(
                new DispatcherQueue(),
                Arbiter.Receive(
                    true,
                    pex,
                    Console.WriteLine
                    )
                );
            Dispatcher.AddCausality(c);
            try
            {
                var are = new AutoResetEvent(false);

                StatemachineDialog<SampleStates> d = Transition_By(are);
                Transition_FromState_By(are, d);
                Transition_FromState_ToState_By(are, d);
                Transition_When_By(are, d);
                Transition_InSyncContext(are, d);
            }
            finally
            {
                Dispatcher.RemoveCausality(c);
            }
        }


        private void Transition_InSyncContext(AutoResetEvent are, StatemachineDialog<SampleStates> d)
        {
            d.Transition<bool>().InSyncContext().By(b => are.Set());
            d.Post(new DialogMessage(true, null));
            Assert.IsTrue(are.WaitOne(500));
        }

        private void Transition_When_By(AutoResetEvent are, StatemachineDialog<SampleStates> d)
        {
            d.Transition<int>().FromState(SampleStates.next2)
                .When(i => i == 42)
                .By(i => are.Set());
            d.Post(new DialogMessage(42, null));
            Assert.IsTrue(are.WaitOne(500));
        }

        private void Transition_FromState_ToState_By(AutoResetEvent are, StatemachineDialog<SampleStates> d)
        {
            d.State = SampleStates.next1;
            d.Transition<int>().FromState(SampleStates.next1)
                .ToState(SampleStates.next2)
                .By(i => are.Set());
            d.Post(new DialogMessage(2, null));
            Assert.IsTrue(are.WaitOne(500));
            Thread.Sleep(500); // wait for setting state to complete
            Assert.AreEqual(SampleStates.next2, d.State);
        }

        private void Transition_FromState_By(AutoResetEvent are, StatemachineDialog<SampleStates> d)
        {
            d.Transition<int>().FromState(SampleStates.start)
                .By(i => are.Set());
            d.Transition<int>().FromState(SampleStates.finish)
                .By(i => Console.WriteLine("wrong handler for int"));
            d.Post(new DialogMessage(1, null));
            Assert.IsTrue(are.WaitOne(500));
            Assert.AreEqual(SampleStates.start, d.State);
        }

        private StatemachineDialog<SampleStates> Transition_By(AutoResetEvent are)
        {
            var d = new StatemachineDialog<SampleStates>(SampleStates.start);
            d.Start();

            d.Transition<string>().By(s => are.Set());
            d.Post(new DialogMessage("hello", null));
            Assert.IsTrue(are.WaitOne(500));
            return d;
        }
    }

    internal enum SampleStates
    {
        start,
        next1,
        next2,
        finish
    }
}
